#include "part2-3_shared.h"

int valid_rule(char *rule);
int generate_kth_password_from_term(LONG_INDEX_PROJ k, char *dest, TERM term, RULE rule);
int generate_kth_password_from_term_symbol(LONG_INDEX_PROJ k, char *dest, TERM_SYMBOL term_symbol, RULE rule);
LONG_INDEX_PROJ power(int base, int exp);
void generate_kth_passowrd_from_word(LONG_INDEX_PROJ k, char *dest, char *word);
void generate_kth_passowrd_from_sequence(LONG_INDEX_PROJ k,char * dest,TERM_SYMBOL term_symbol, int seqlength);

#define LETTERS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define NUMBERS "0123456789"
#define ALPHANUM "abcdefghijklmnopqrstuvwxyz0123456789!?~"


int word_length(char *string) {
	int i = 0;
	int count = 0;
	while (string[i] != '\0') {
		if ((isupper(string[i])) || (islower(string[i]))) count++;
		i++;
	}
	return count;
}
int create_rule(RULE *rule, char *rule_as_string, char **lexicon, LONG_INDEX_PROJ lexicon_size) {

	int i = 0, k = 0, m = 0, j = 0 , l = 0, s = 0;
	char c;
	int number_of_terms = 1;
	char current_term_as_string[MAX_SIZE];
	char sequence[9] = "";
	int number_of_symbols_for_current_term = 0;
	int p = 0;

	if (!valid_rule(rule_as_string)) {
		fprintf(stderr,"Error: rule %s does not fit syntax.\n",rule_as_string);
		return -1;
	}

	while ( (c = rule_as_string[i++]) != '\0') {
		if (c == '+') number_of_terms++;
	}
	rule->terms = (TERM *)malloc(number_of_terms * sizeof(TERM));
	if (rule->terms == NULL) {
		fprintf(stderr,"Memory allocation problem");
		return -2;
	}
	rule->number_of_terms = number_of_terms;
	rule->lexicon_size = lexicon_size;
	rule->lexicon = lexicon;
	i = 0;

	while ( (c = rule_as_string[i]) != '\0') {
		if (c == ' ') {
			i++;
			continue;
		}
		p = 0;
		while (((c = rule_as_string[i]) != '+') && ((c = rule_as_string[i]) != '\0'))  {
			current_term_as_string[p++] = c;
			i++;
		}
		current_term_as_string[p] = '\0';
		p = 0;
		number_of_symbols_for_current_term = 0;
		while (current_term_as_string[p++] != '\0') {
			if ((!isdigit(current_term_as_string[p])) && (current_term_as_string[p] != ' ')) number_of_symbols_for_current_term++;
		}

		rule->terms[k].number_of_symbols = number_of_symbols_for_current_term;
		rule->terms[k].symbols = (TERM_SYMBOL *)malloc(number_of_symbols_for_current_term * sizeof(TERM_SYMBOL));
		if (rule->terms[k].symbols == NULL) {
			fprintf(stderr,"Memory allocation problem");
			return -2;
		}
		for (m = 0; m < number_of_symbols_for_current_term ; m++) {
			rule->terms[k].symbols[m].symbol = current_term_as_string[m + l];
			if (rule->terms[k].symbols[m].symbol == '^') {
				j = 0;
				while (isdigit(current_term_as_string[m + l + 1])) {
					sequence[j++] = current_term_as_string[m + l + 1];
					l++;
				}
				rule->terms[k].symbols[m].n = atoi(sequence);
				rule->terms[k].symbols[m].number_of_possible_passwords = 0;
				for (s = 0 ; s <= rule->terms[k].symbols[m].n ; s++) rule->terms[k].symbols[m].number_of_possible_passwords +=  power(DIGIT_RANGE, s);
				rule->terms[k].symbols[m].possible_values = NUMBERS;
			}
			if (rule->terms[k].symbols[m].symbol == '*') {
				j = 0;
				while (isdigit(current_term_as_string[m + l + 1])) {
					sequence[j++] = current_term_as_string[m + l + 1];
					l++;
				}
				rule->terms[k].symbols[m].n = current_term_as_string[m + 1] - 48;
				rule->terms[k].symbols[m].number_of_possible_passwords = 0;
				for (s = 0 ; s <= rule->terms[k].symbols[m].n ; s++) rule->terms[k].symbols[m].number_of_possible_passwords +=  power(LETTER_RANGE, s);
				rule->terms[k].symbols[m].possible_values = LETTERS;
				
			}
			if (rule->terms[k].symbols[m].symbol == '%') {
				j = 0;
				while (isdigit(current_term_as_string[m + l + 1])) {
					sequence[j++] = current_term_as_string[m + l + 1];
					l++;
				}
				rule->terms[k].symbols[m].n = current_term_as_string[m + 1] - 48;
				rule->terms[k].symbols[m].number_of_possible_passwords = 0;
				for (s = 0 ; s <= rule->terms[k].symbols[m].n ; s++) {
					rule->terms[k].symbols[m].number_of_possible_passwords +=  power(ALPHANUM_RANGE, s);
				}
				rule->terms[k].symbols[m].possible_values = (char *)malloc((ALPHANUM_RANGE + 1) * sizeof(char));
				if (rule->terms[k].symbols[m].possible_values == NULL) {
					fprintf(stderr,"Memory allocation problem");
					return -2;
				}
				rule->terms[k].symbols[m].possible_values = ALPHANUM;
			}
			if (rule->terms[k].symbols[m].symbol == '?') {
				rule->terms[k].symbols[m].n = 1;
				rule->terms[k].symbols[m].number_of_possible_passwords = ASCII_RANGE;
				rule->terms[k].symbols[m].possible_values = (char *)malloc((ASCII_RANGE + 1) * sizeof(char));
				if (rule->terms[k].symbols[m].possible_values == NULL) {
					fprintf(stderr,"Memory allocation problem");
					return -2;
				}
				for (j = 0 ; j < ASCII_RANGE ; j++) {
					rule->terms[k].symbols[m].possible_values[j] = j + 32; 
				}
			}
			if (rule->terms[k].symbols[m].symbol == '@') {
				rule->terms[k].symbols[m].n = 1;
				rule->terms[k].symbols[m].number_of_possible_passwords = rule->lexicon_size;
				rule->terms[k].symbols[m].possible_values = NULL;
			}
			if (rule->terms[k].symbols[m].symbol == '#') {
				rule->terms[k].symbols[m].n = 1;
				rule->terms[k].symbols[m].possible_values = NULL;
				rule->terms[k].symbols[m].number_of_possible_passwords = 0;
				for (j = 0 ; j < rule->lexicon_size ; j++) {
					rule->terms[k].symbols[m].number_of_possible_passwords += power(2,word_length(*(rule->lexicon + j)));
				}
			}
		}
		rule->terms[k].number_of_possible_passwords = 1;
		for (j = 0 ; j < rule->terms[k].number_of_symbols; j++) {
			rule->terms[k].number_of_possible_passwords *= rule->terms[k].symbols[j].number_of_possible_passwords;
		}
		k++;
		if (c == '+') i++;
		l = 0;
	}
	rule->number_of_possible_passwords = 0;
	for (i = 0 ; i < rule->number_of_terms ; i++) {
		rule->number_of_possible_passwords += rule->terms[i].number_of_possible_passwords;
	}

	return 1;
}

int valid_rule(char *rule) {

	int i = 0;
	char c;
	int number = 0;

	if ( (rule[i] == '\0') || (rule[i] == '+')) {
		return 0;
	}

	if (strlen(rule) == 0) { /* empty rule is illegal */
		return 0;
	}

	if (rule[0] == ' ') {
		return 0;
	}
	while ( (c = rule[i]) != '\0') {
		if (c == '@' || c == '#' || c == '?') {
			i++;
			continue;
		}
		if (c == '^' || c == '*' || c == '%') {
			while (isdigit(c = rule[++i])) number = 1;
			if (number == 0) {
				return 0;
			}
			continue;
		}
		if (c == ' ') {
			while ( (c = rule[i]) == ' ') i++;
			if ((c = rule[i]) != '+') {
				if ((c = rule[i]) == '\0') return 1;
				return 0;
			}
		}
		if ((c == '+' && rule[i+1] == '\0') || (c == '+' && rule[i+1] == '+')) return 0;
		if (c == '+') {
			i++;
			while (rule[i] == ' ') i++;
			continue;
		}
		else {
			return 0;
		}
	}
	return 1;
}

int generate_kth_password(LONG_INDEX_PROJ k, char *dest, RULE rule) {
	
	int i;
	if (k < 0 || k > rule.number_of_possible_passwords) return 0;
	for (i = 0 ; i < rule.number_of_terms ; i++) {
		if (k <= rule.terms[i].number_of_possible_passwords) break; 
		k = k - rule.terms[i].number_of_possible_passwords;
	}

	generate_kth_password_from_term(k,dest,rule.terms[i],rule);
	if (!strcmp(dest,"")) {
		generate_kth_password( (k+1) % rule.number_of_possible_passwords, dest,rule);
	}
	return 1;
}

int generate_kth_password_from_term(LONG_INDEX_PROJ k, char *dest, TERM term, RULE rule) {
	
	char current_pass[MAX_PASSWORD_LENGTH] = "";
	int i;
	dest[0] = '\0';
	for (i = 0; i < term.number_of_symbols; i++) {
		generate_kth_password_from_term_symbol((k % term.symbols[i].number_of_possible_passwords), current_pass, term.symbols[i], rule);
		dest = strcat(dest,current_pass);
		current_pass[0] = '\0';
		k = k / term.symbols[i].number_of_possible_passwords;
	}
	return 1;
}

int generate_kth_password_from_term_symbol(LONG_INDEX_PROJ k, char *dest, TERM_SYMBOL term_symbol, RULE rule) {
	
	int i;
	LONG_INDEX_PROJ number_of_passwords_per_word = 0;
	LONG_INDEX_PROJ number_of_passwords_per_sequence_length = 0;
	char **lex = rule.lexicon;

	char c = term_symbol.symbol;

	switch (c) {
	case '@':

		strcpy(dest,*(lex + k));
		return 1;

	case '#':

		for (i = 0 ; i < rule.lexicon_size ; i++) {
			number_of_passwords_per_word = power(2,word_length(*(lex + i)));
			if (k <= number_of_passwords_per_word) {
				generate_kth_passowrd_from_word(k, dest, *(lex + i));
				break;
			}
			k = k - number_of_passwords_per_word;
		}
		return 1;

	case '^':
		for (i = 0; i <= term_symbol.n; i++) {
			number_of_passwords_per_sequence_length = power(DIGIT_RANGE,i);
			if (k < number_of_passwords_per_sequence_length) {
				generate_kth_passowrd_from_sequence(k, dest, term_symbol, i);
				break;
			}
			k = k - number_of_passwords_per_sequence_length;
		}
		return 1;

	case '*':

		for (i = 0; i <= term_symbol.n; i++) {
			number_of_passwords_per_sequence_length = power(LETTER_RANGE,i);
			if (k < number_of_passwords_per_sequence_length) {
				generate_kth_passowrd_from_sequence(k, dest, term_symbol,i);
				break;
			}
			k = k - number_of_passwords_per_sequence_length;
		}
		return 1;

	case '%':

		for (i = 0; i <= term_symbol.n; i++) {
			number_of_passwords_per_sequence_length = power(ALPHANUM_RANGE,i);
			if (k < number_of_passwords_per_sequence_length) {
				generate_kth_passowrd_from_sequence(k, dest, term_symbol,i);
				break;
			}
			k = k - number_of_passwords_per_sequence_length;
		}
		return 1;

	case '?':

			dest[0] = term_symbol.possible_values[k % ASCII_RANGE];
			dest[1] = '\0';
		
		return 1;

	}
	return 1;

}

void generate_kth_passowrd_from_word(LONG_INDEX_PROJ k, char *dest, char *word) {

	int i;
	for (i = 0 ; word[i] != '\0' ; i++) {
		if (islower(word[i]) || isupper(word[i])) {
			if ((k % 2) == 0) dest[i] = tolower(word[i]);
			if ((k % 2) == 1) dest[i] = toupper(word[i]);
			k = k / 2;
		}
		else dest[i] = word[i];
	}
	dest[i] = '\0';

}

void generate_kth_passowrd_from_sequence(LONG_INDEX_PROJ k,char * dest,TERM_SYMBOL term_symbol, int seqlength) {

	LONG_INDEX_PROJ range;
	int i;
	if (seqlength == 0) {
		dest = "";
		return;
	}
	if (term_symbol.symbol == '^') range = DIGIT_RANGE;
	if (term_symbol.symbol == '*') range = LETTER_RANGE;
	if (term_symbol.symbol == '%') range = ALPHANUM_RANGE;

	for (i = 0 ; i < seqlength ; i++) {
		dest[i] = term_symbol.possible_values[k % range];
		k = k / range;
	}
	dest[i] = '\0';

}

LONG_INDEX_PROJ power(int base, int exp) {
	int i;
	LONG_INDEX_PROJ ret = 1;
	for (i = 0; i < exp; i++)
		ret *= base;
	return ret;
}

char** load_lexicon(char *lexfile_path, LONG_INDEX_PROJ *length){

	char **lexicon;
	FILE *lexfile;
	char *lexicon_word;
	LONG_INDEX_PROJ size;
	int buffer_pointer = 0;
	int i = 0;
	int linesize = 0;
	int number_of_bytes_read = 0;
	int j = 0;
	LONG_INDEX_PROJ count = 0;
	int empty_line = 0;
	int status = 0;

	/* check file exists */
	lexfile = fopen(lexfile_path,"r");
	if (lexfile == NULL) {
		perror(lexfile_path);
		exit(1);
	}
	
	status = fseek(lexfile , 0, SEEK_END);
	if (status != 0) { 
		perror(lexfile_path);
		exit(1);
	}
	size = ftell(lexfile );

	lexicon_word = (char*) malloc((size + 1)*sizeof(char));
	if (lexicon_word == NULL) {
		fprintf(stderr,"Memory allocation failed");
		exit(1);
	}

	status = fseek(lexfile,0,SEEK_SET);
	if (status) {
		perror(lexfile_path);
		exit(1);
	}
	number_of_bytes_read = fread(lexicon_word,1,size,lexfile);
	if (number_of_bytes_read == 0) {
		fprintf(stderr,"lexicon file is empty\n");
		exit(1);
	}
	fclose(lexfile);
	lexicon_word[number_of_bytes_read] = '\0';

	if (lexicon_word[0] == '\n') empty_line = 1;

	while (j <= number_of_bytes_read) {
		if ((lexicon_word[j] == '\0') && (lexicon_word[j - 1] != '\n')) { /* '\0' means it the end of the buffer */
			count++;
			break;
		}
		if (lexicon_word[j] == '\n') {
			if (lexicon_word[j+1] == '\n') empty_line = 1;
			count++;
		}
		j++;
	}
	/* make sure there is an empty line in the file */
	if (!empty_line) {
		fprintf(stderr,"Invalid lexicon file, must contain at leas one empty line");
		exit(1);
	}

	lexicon = (char**)malloc(count*sizeof(char*));
	if (lexicon == NULL) {
		fprintf(stderr,"Memory allocation problem");
		exit(1);
	}

	empty_line = 0;
	j = 0;
	while (i < count) {
		if (lexicon_word[j] == '\n') { /* this is an empty line */
			empty_line = 1;
			linesize = 0;
		}
		while ((lexicon_word[j] != '\n') && (lexicon_word[j] != '\0') && (!empty_line)) {
			j++;
			linesize++;
		}
		j++;
		lexicon[i] = (char*)malloc((linesize + 1) * sizeof(char));
		if (lexicon[i] == NULL) {
			fprintf(stderr,"Memory allocation problem");
			exit(1);
		}
		memcpy(lexicon[i],lexicon_word + buffer_pointer,linesize);
		lexicon[i][linesize] = '\0';
		buffer_pointer = j;
		linesize = 0;
		empty_line = 0;
		i++;
	}

	free(lexicon_word);
	*length = count;
	return lexicon;
}

void free_rule(RULE *rule) {
	int i,j;
	for (i = 0 ; i < rule->number_of_terms ; i++) {
		for (j = 0 ; j < rule->terms[i].number_of_symbols ; j++) {
			if (rule->terms[i].symbols[j].symbol == '?') {
				free(rule->terms[i].symbols[j].possible_values);
			}
		}
		free(rule->terms[i].symbols);
	}
	free(rule->terms);
}

void free_lexicon(char **lexicon, LONG_INDEX_PROJ length) {
	int i;
	for (i = 0 ; i < length ; i++) {
		free(lexicon[i]);
	}
	free(lexicon);
}

int cryptHashToTable(const unsigned char * x, int inputLength, int numOfEntries) {
	LONG_INDEX_PROJ hashResult = pseudo_random_function(x, inputLength, (LONG_INDEX_PROJ) TABLE_SEED);
	return (hashResult % 65536) % numOfEntries;

}

int cryptHashTo64bit(const unsigned char * x, int inputLength, unsigned char * result) {
	LONG_INDEX_PROJ hashResult = pseudo_random_function(x, inputLength, (LONG_INDEX_PROJ) VALIDATION_SEED);
	memcpy( result, (unsigned char *)&hashResult, sizeof(LONG_INDEX_PROJ) );
	return sizeof(LONG_INDEX_PROJ);
	
}